#include "PrecompiledHeaderRenderer_OpenGL.h"
#include "FramebufferWrapper.h"

/*--------------------------------------------------------------------------*/
GLFramebuffer* FramebufferWrapper::_boundFramebuffer = NULL;
int FramebufferWrapper::_framebuffersCount = 0;
std::map<std::string, GLFramebuffer*> FramebufferWrapper::_framebuffersMap;
/*--------------------------------------------------------------------------*/
GLenum gl_colorAttachments[] = { GL_COLOR_ATTACHMENT0,
                                 GL_COLOR_ATTACHMENT1,
                                 GL_COLOR_ATTACHMENT2,
                                 GL_COLOR_ATTACHMENT3,
                                 GL_COLOR_ATTACHMENT4,
                                 GL_COLOR_ATTACHMENT5,
                                 GL_COLOR_ATTACHMENT6,
                                 GL_COLOR_ATTACHMENT7,
                                 GL_COLOR_ATTACHMENT8,
                                 GL_COLOR_ATTACHMENT9,
                                 GL_COLOR_ATTACHMENT10,
                                 GL_COLOR_ATTACHMENT11,
                                 GL_COLOR_ATTACHMENT12,
                                 GL_COLOR_ATTACHMENT13,
                                 GL_COLOR_ATTACHMENT14,
                                 GL_COLOR_ATTACHMENT15
                               };

/*--------------------------------------------------------------------------*/

FramebufferWrapper::FramebufferWrapper()
{
}

/*--------------------------------------------------------------------------*/

FramebufferWrapper::~FramebufferWrapper()
{
	for (auto i = _framebuffersMap.begin(); i != _framebuffersMap.end(); ++i)
	{
		AssertLogE(true, "Framebuffer %s not deleted!", i->first.c_str());
	}

	AssertLogC(_framebuffersMap.size() != 0, "Some Framebuffers left undeleted!");
}

/*--------------------------------------------------------------------------*/

GLFramebuffer* FramebufferWrapper::GenFramebuffer(const std::string& name)
{
	AssertLogC(name == "", "Zero length name");
	AssertLogC(_framebuffersMap[name] != NULL, "Framebuffer %s already exists!", name.c_str());

	GLFramebuffer* fbos = new GLFramebuffer(name);
	glGenFramebuffers(1, &fbos->handle);

	glBindFramebuffer(GL_FRAMEBUFFER, fbos->handle);
	_boundFramebuffer = fbos;

	_framebuffersMap[name] = fbos;

	_framebuffersCount += 1;
	return fbos;
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::DeleteFramebuffer(GLFramebuffer** fb)
{
	if (fb && *fb)
	{
		_framebuffersCount -= 1;

		/*--------------------------------------------------------------------------*/

		auto i = _framebuffersMap.find((*fb)->name);
		AssertLogC(i == _framebuffersMap.end(), "Framebuffer %s not found in map", (*fb)->name.c_str());
		_framebuffersMap.erase(i);

		/*--------------------------------------------------------------------------*/

		glDeleteFramebuffers(1, &(*fb)->handle);
		delete *fb;
		*fb = NULL;
	}
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::SetFramebufferColorAttachment(GLFramebuffer* fb, int mountingPoint, GLTexture* t)
{
	AssertLogC(t == NULL, "Cannot attach NULL as ColorAttachment %d", mountingPoint);
	AssertLogC(t->handle == 0, "Cannot attach 0 texture to FBO %s", fb->name.c_str());
	AssertLogC(GL_COLOR_ATTACHMENT0 + mountingPoint > GL_COLOR_ATTACHMENT15, "Cannot attach texture at GL_COLOR_ATTACHMENT0 + %d", mountingPoint);

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb->handle);

	if ((t->size.x != fb->width || t->size.y != fb->height))
	{
		if (fb->width != 0 || fb->height != 0)
		{
			AssertLogC(true, "Size of target %s and framebuffer %s are different", t->name.c_str(), fb->name.c_str());
		}
		else
		{
			fb->width = t->size.x;
			fb->height = t->size.y;
		}
	}

	if (t != NULL)
	{
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + mountingPoint, GL_TEXTURE_2D, t->handle, 0);
		fb->attachments.push_back(GL_COLOR_ATTACHMENT0 + mountingPoint);
		fb->attachmentsPtn.push_back(t);
	}
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::SetFramebufferDepthAttachment(GLFramebuffer* fb, GLTexture* t)
{
	AssertLogC(fb == NULL, "NULL FBO!");
	AssertLogC(t == NULL, "NULL Texture!");

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb->handle);
	TextureWrapper::BindTextureAt(0, t);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, t->size.x, t->size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);

	if ((t->size.x != fb->width || t->size.y != fb->height))
	{
		if (fb->width != 0 || fb->height != 0)
		{
			AssertLogC(true, "Size of target %s and framebuffer %s are different", t->name.c_str(), fb->name.c_str());
		}
		else
		{
			fb->width = t->size.x;
			fb->height = t->size.y;
		}
	}

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, t->handle, 0);
	fb->depthAttachment = t;
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::ValidateFramebuffer(GLFramebuffer* fbo)
{
	AssertLogC(fbo == NULL, "Framebuffer is NULL.");
	AssertLogC(fbo->handle == 0, "Framebuffer is 0.");

	glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle);
	glDrawBuffers(fbo->attachments.size(), &fbo->attachments[0]);

	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	if (status == GL_FRAMEBUFFER_COMPLETE)
	{
		fbo->validated = true;
	}
	else
	{
		AssertLogC(status != GL_FRAMEBUFFER_COMPLETE, "Framebuffer validation failed: %d", status);
	}

	UnbindFramebuffer();

#ifdef RENDERER_WRITE_LOGS
	std::ofstream file("../Logs/FRAMEBUFFER_" + fbo->name + ".log");
	file << *fbo;
	file.close();
#endif
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::BindFramebuffer(GLFramebuffer* fbo, bool force)
{
	if (force || _boundFramebuffer != fbo)
	{
		_boundFramebuffer = fbo;

		if (_boundFramebuffer == NULL)
		{
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
			//glDrawBuffer(GL_BACK);
		}
		else
		{
			AssertLogC(fbo->validated == false, "Using invalid FBO %s", fbo->name.c_str());
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo->handle);
		}
	}
}

/*--------------------------------------------------------------------------*/

void FramebufferWrapper::UnbindFramebuffer()
{
	_boundFramebuffer = NULL;
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

/*--------------------------------------------------------------------------*/